package airacrobats.control;

import airacrobats.game.Camera;
import airacrobats.game.object.Airplane;
import airacrobats.io.data.MovementData;
import airacrobats.util.Util;
import com.centralnexus.input.Joystick;
import com.centralnexus.input.JoystickListener;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.List;
import javax.swing.JPanel;
import javax.vecmath.Point4f;

/**
 * This class has the Controller properties plus the features of controlling 
 * the airplane with the mouse/joystick.
 */
public class PlayerController extends Controller implements MouseMotionListener, KeyListener, JoystickListener
{
    /**
     * The info panel size.
     */
    private static final Dimension INFO_PANEL_SIZE = new Dimension(120, 20);

    /**
     * Factor to reduce the stick sensibility for the mouse.
     */
    private static final int STICK_SENSIBILITY = 10;
    /**
     * The stick size.
     */
    private static final int STICK_SIZE = 30;

    /**
     * The joystick.
     */
    private Joystick joystick;
    /**
     * The game camera.
     */
    private Camera camera;
    /**
     * The pad size for the mouse.
     */
    private Dimension padSize;
    /**
     * Stick x.
     */
    private float stickX;
    /**
     * Stick y.
     */
    private float stickY;
    
    /**
     * @param camera The camera.
     * @param airplane The airplane.
     * @param padSize The size of the mouse pad.
     * @param movements The movements of the route.
     */
    public PlayerController(Camera camera, Airplane airplane, Dimension padSize,
            List<MovementData> movements)
    {
        super(airplane, movements);
        this.camera = camera;
        this.padSize = padSize;
    }

    /**
     * Setups the joystick.
     * @return True on success.
     */
    public boolean setupJoystick()
    {
        try
        {
            joystick = Joystick.createInstance();
            joystick.addJoystickListener(this);
        }
        catch (Exception ex)
        {
            joystick = null;
            return false;
        }

        return true;
    }

    public void blockJoystick()
    {
        if (joystick != null)
            joystick.removeJoystickListener(this);
    }

    /**
     * Paints the stick on the screen. Also paints the desired movement.
     * @param g The graphics.
     */
    public void paintMovement(Graphics g)
    {
        if (currentMovement != null)
        {
            // Paints where the stick must be.
            g.setColor(Color.ORANGE);
            g.fillArc(padSize.width * (50 + currentMovement.getDeltaX() / 2) / 100 - STICK_SIZE / 2,
                    padSize.height * (50 + currentMovement.getDeltaY() / 2) / 100 - STICK_SIZE / 2,
                    STICK_SIZE, STICK_SIZE, 0, 360);
            g.setColor(Color.WHITE);
            g.drawArc(padSize.width * (50 + currentMovement.getDeltaX() / 2) / 100 - STICK_SIZE / 2,
                    padSize.height * (50 + currentMovement.getDeltaY() / 2) / 100 - STICK_SIZE / 2,
                    STICK_SIZE, STICK_SIZE, 0, 360);

            // Paints warnings about the airplane engine power.
            int deltaEngine = currentMovement.getEnginePowerGain() - airplane.getEnginePower();
            if (deltaEngine > 0)
            {
                int height = g.getFontMetrics().getHeight();
                String message = "SPEED UP " + deltaEngine + "%!!!";
                int width = g.getFontMetrics().stringWidth(message);
                Util.drawOutlinedString(message, Color.ORANGE, Color.BLACK, g,
                        (padSize.width - width) / 2, padSize.height / 2 - STICK_SIZE + height / 2);
            }
            else if (deltaEngine < 0)
            {
                int height = g.getFontMetrics().getHeight();
                String message = "SPEED DOWN " + deltaEngine + "%!!!";
                int width = g.getFontMetrics().stringWidth(message);
                Util.drawOutlinedString(message, Color.ORANGE, Color.BLACK, g,
                        (padSize.width - width) / 2, padSize.height / 2 - STICK_SIZE + height / 2);
            }

            // Paints warnings about the smoke movements.
            if (currentMovement.isSmokeOn() != airplane.isSmokeOn())
            {
                String message;
                if (currentMovement.isSmokeOn())
                    message = "TURN ON SMOKE!!!";
                else
                    message = "TURN OFF SMOKE!!!";

                int width = g.getFontMetrics().stringWidth(message);
                Util.drawOutlinedString(message, Color.WHITE, Color.BLACK, g,
                        (padSize.width - width) / 2, padSize.height / 2 + STICK_SIZE);
            }
        }

        g.setColor(Color.RED);
        g.fillArc(padSize.width / 2 + (int) (stickX * padSize.width / 2.0f) - STICK_SIZE / 2,
                padSize.height / 2 - (int) (stickY * padSize.height / 2.0f) - STICK_SIZE / 2,
                STICK_SIZE, STICK_SIZE, 0, 360);
        g.setColor(Color.WHITE);
        g.drawArc(padSize.width / 2 + (int) (stickX * padSize.width / 2.0f) - STICK_SIZE / 2,
                padSize.height / 2 - (int) (stickY * padSize.height / 2.0f) - STICK_SIZE / 2,
                STICK_SIZE, STICK_SIZE, 0, 360);
    }

    /**
     * Creates the info panel and adds it to the owner panel.
     * @param owner The owner panel.
     * @param c The grid bag constraints.
     */
    public void createInfoPanel(JPanel owner, GridBagConstraints c)
    {
        JPanel panel = new JPanel()
        {
            @Override
            protected void paintComponent(Graphics g)
            {
                int height = g.getFontMetrics().getHeight();
                if (joystick != null)
                    Util.drawOutlinedString("Jostick ON", Color.BLUE, Color.BLACK, g, 2, height);
                else
                    Util.drawOutlinedString("Jostick OFF", Color.RED, Color.BLACK, g, 2, height);
            }
        };
        panel.setPreferredSize(INFO_PANEL_SIZE);
        panel.setMinimumSize(INFO_PANEL_SIZE);
        owner.add(panel, c);
    }

    /**
     */
    public void joystickAxisChanged(Joystick joystick)
    {
        stickX = joystick.getX();
        stickY = -joystick.getY();

        airplane.setRotation(-Airplane.MAX_ROLL * stickX, Airplane.MAX_PITCH * stickY);
    }

    /**
     */
    public void joystickButtonChanged(Joystick joystick)
    {
        airplane.setSmokeOn((joystick.getButtons() & 0x01) == 1);
    }

    /**
     */
    @Override
    public void mouseMoved(MouseEvent e)
    {
        // Multiplies by 2 because the mouse only go half (0.5) of the pad.
        stickX = 2.0f * (e.getX() - padSize.width / 2) / (float) padSize.width;
        stickY = -2.0f * (e.getY() - padSize.height / 2) / (float) padSize.height;

        // Applying mouse sensibility.
        float deltaY = (int) (100.0f * stickY) / STICK_SENSIBILITY;
        deltaY = deltaY / 100.0f * STICK_SENSIBILITY;
        float deltaX = (int) (100.0f * stickX) / STICK_SENSIBILITY;
        deltaX = deltaX / 100.0f * STICK_SENSIBILITY;

        airplane.setRotation(-Airplane.MAX_ROLL * deltaX, Airplane.MAX_PITCH * deltaY);
    }

    /**
     */
    public void keyTyped(KeyEvent e)
    {

    }

    /**
     */
    public void keyPressed(KeyEvent e)
    {
        if (e.getKeyCode() == KeyEvent.VK_9)
        {
            camera.changeCamera(false, true);
            camera.setVisionStructure(new Point4f(-5.0f, 2.0f, 0.0f, 0));
        }
        else if (e.getKeyCode() == KeyEvent.VK_8)
        {
            camera.changeCamera(false, false);
            camera.setVisionStructure(new Point4f(-0.2f, 0.3f, 0.0f, 30));
        }
        else if (e.getKeyCode() == KeyEvent.VK_7)
        {
            camera.changeCamera(false, false);
            camera.setVisionStructure(new Point4f(-1.5f, 0.3f, 0.0f, 10));
        }
        else if (e.getKeyCode() == KeyEvent.VK_NUMPAD6)
        {
            camera.getVisionStructure().z -= 0.01f;
        }
        else if (e.getKeyCode() == KeyEvent.VK_NUMPAD4)
        {
            camera.getVisionStructure().z += 0.01f;
        }
        else if (e.getKeyCode() == KeyEvent.VK_NUMPAD8)
        {
            camera.getVisionStructure().x += 0.01f;
        }
        else if (e.getKeyCode() == KeyEvent.VK_NUMPAD2)
        {
            camera.getVisionStructure().x -= 0.01f;
        }
        else if (e.getKeyCode() == KeyEvent.VK_NUMPAD7)
        {
            camera.getVisionStructure().y += 0.01f;
        }
        else if (e.getKeyCode() == KeyEvent.VK_NUMPAD1)
        {
            camera.getVisionStructure().y -= 0.01f;
        }
        else if (e.getKeyCode() == KeyEvent.VK_NUMPAD9)
        {
            camera.getVisionStructure().w += 1;
        }
        else if (e.getKeyCode() == KeyEvent.VK_NUMPAD3)
        {
            camera.getVisionStructure().w -= 1;
        }
        else if (e.getKeyCode() == KeyEvent.VK_W || e.getKeyCode() == KeyEvent.VK_S)
        {
            int increment = e.getKeyCode() == KeyEvent.VK_W ? 1 : -1;
            airplane.setEnginePower(Math.max(0, Math.min(100, airplane.getEnginePower() + increment)));
        }
        else if (e.getKeyCode() == KeyEvent.VK_F)
        {
            airplane.setSmokeOn(true);
        }
    }

    /**
     */
    public void keyReleased(KeyEvent e)
    {
        if (e.getKeyCode() == KeyEvent.VK_F)
        {
            airplane.setSmokeOn(false);
        }
    }

    /**
     */
    public void mouseDragged(MouseEvent e)
    {

    }

    /**
     */
    @Override
    protected int doUpdate(long deltaTime)
    {
        float score = 0;
        if (currentMovement != null)
        {
            // Correct stick values will give 2 points/second (minimum is 1).
            float deltaX = Math.abs(stickX - (currentMovement.getDeltaX() / 100.0f));
            float deltaY = Math.abs(stickY - (currentMovement.getDeltaY() / 100.0f));
            score = (float) (deltaTime / 1000000000.0f * (Math.sqrt(1.0f -
                    Math.sqrt(deltaX * deltaX + deltaY * deltaY)) + 1.0f));
            
            // Correct engine power values will give 5 points/second.
            score += deltaTime / 1000000000.0f *
                    (-0.05 * Math.abs(airplane.getEnginePower() - currentMovement.getEnginePowerGain()) + 5.0f);

            // Correct smoke movements will give 5 points/second.
            if (airplane.isSmokeOn() == currentMovement.isSmokeOn())
                score += deltaTime / 1000000000.0f * 5.0f;
        }

        // Multiplies the final score by 100.
        score *= 100;
        
        return (int) score;
    }
}
